﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MugenMvvmToolkit.Interfaces;

namespace MugenMvvmToolkit.Infrastructure
{
    /// <summary>
    ///   Represents the container that contains an object which can listen any events.
    /// </summary>
#if WPF
    [Serializable]
#endif
    public class WeakListenersCollection : ICollection<object>
    {
        #region Nested types

#if WPF
        [Serializable]
#endif
        internal sealed class WeakHandlerWrapper : IHandler
        {
            #region Fields

            private readonly List<object> _list;
            private readonly Action<List<object>, IHandler> _removeCallback;
            private readonly WeakReference _weakReference;
            private readonly Dictionary<Type, MethodInfo> _handlers = new Dictionary<Type, MethodInfo>();

            #endregion

            #region Constructors

            /// <summary>
            ///     Initializes a new instance of the <see cref="WeakHandlerWrapper" /> class.
            /// </summary>
            public WeakHandlerWrapper(object handler, List<object> list, Action<List<object>, IHandler> removeCallback)
            {
                InitializeHandlers(handler);
                if (!HasHandlers) return;
                _weakReference = new WeakReference(handler);
                _list = list;
                _removeCallback = removeCallback;
            }

            #endregion

            #region Properties

            public bool HasHandlers
            {
                get { return _handlers.Count != 0; }
            }

            #endregion

            #region Methods

#if NETFX_CORE
            private void InitializeHandlers(object handler)
            {
                var interfaces = handler.GetType().GetTypeInfo().ImplementedInterfaces
                                    .Where(x => x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == typeof(Interfaces.IHandler<>))
                                    .ToArray();

                foreach (var @interface in interfaces)
                {
                    var type = @interface.GenericTypeArguments[0];
                    var method = @interface.GetTypeInfo().GetDeclaredMethod("Handle");
                    _handlers[type] = method;
                }
            }
#else
            private void InitializeHandlers(object handler)
            {
                var interfaces = handler.GetType().GetInterfaces()
                                    .Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IHandler<>))
                                    .ToArray();

                foreach (var @interface in interfaces)
                {
                    var type = @interface.GetGenericArguments()[0];
                    var method = @interface.GetMethod("Handle");
                    _handlers[type] = method;
                }
            }
#endif


            #endregion

            #region Implementation of IHandler<in object>

            /// <summary>
            ///     Handles the message.
            /// </summary>
            /// <param name="sender">The object that raised the event.</param>
            /// <param name="message">Information about event.</param>
            public void Handle(object sender, object message)
            {
                var target = _weakReference.Target;
                if (target == null)
                {
                    _removeCallback(_list, this);
                    return;
                }
                var messageType = message.GetType();
                foreach (var pair in _handlers)
                {
                    if (!pair.Key.IsAssignableFrom(messageType)) continue;
                    pair.Value.Invoke(target, new[] { sender, message });
                }
            }

            #endregion
        }

#if WPF
        [Serializable]
#endif
        private struct MessageSenderCache : IEquatable<MessageSenderCache>
        {
            #region Equality members

            /// <summary>
            /// Indicates whether the current object is equal to another object of the same type.
            /// </summary>
            /// <returns>
            /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
            /// </returns>
            /// <param name="other">An object to compare with this object.
            ///                 </param>
            public bool Equals(MessageSenderCache other)
            {
                return Equals(_sender, other._sender) && Equals(_message, other._message);
            }

            /// <summary>
            /// Indicates whether this instance and a specified object are equal.
            /// </summary>
            /// <returns>
            /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
            /// </returns>
            /// <param name="obj">Another object to compare to. 
            ///                 </param>
            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj)) return false;
                return obj is MessageSenderCache && Equals((MessageSenderCache)obj);
            }

            /// <summary>
            /// Returns the hash code for this instance.
            /// </summary>
            /// <returns>
            /// A 32-bit signed integer that is the hash code for this instance.
            /// </returns>
            public override int GetHashCode()
            {
                unchecked
                {
                    return ((_sender != null ? _sender.GetHashCode() : 0) * 397) ^ (_message != null ? _message.GetHashCode() : 0);
                }
            }

            public static bool operator ==(MessageSenderCache left, MessageSenderCache right)
            {
                return left.Equals(right);
            }

            public static bool operator !=(MessageSenderCache left, MessageSenderCache right)
            {
                return !left.Equals(right);
            }

            #endregion

            #region Fields

            private readonly object _sender;
            private readonly object _message;

            #endregion

            #region Constructors

            public MessageSenderCache(object sender, object message)
            {
                _sender = sender;
                _message = message;
            }

            #endregion
        }

        #endregion

        #region Fields

        /// <summary>
        ///     Gets the dictionary that contains weakrefernce of an handler and all handler that provide this object.
        /// </summary>
        protected readonly Dictionary<WeakReference, List<object>> ItemsToHandlers;

        /// <summary>
        ///     Gest the collection of listeners.
        /// </summary>
        protected readonly WeakReferenceCollection<object> Listeners;

        private static readonly Action<List<object>, IHandler> NodoRemoveCallback = (list, handler) => { };
        private readonly Dictionary<MessageSenderCache, bool> _updatedMessages;
        private readonly object _locker;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="WeakListenersCollection" /> class.
        /// </summary>
        public WeakListenersCollection(params object[] handlers)
        {
            _locker = new object();
            _updatedMessages = new Dictionary<MessageSenderCache, bool>();
            Listeners = new WeakReferenceCollection<object>();
            ItemsToHandlers = new Dictionary<WeakReference, List<object>>();
            if (handlers == null) return;
            foreach (object item in handlers)
                Add(item);
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Notifies listener about an event.
        /// </summary>
        /// <param name="target">The specified listener to notify.</param>
        /// <param name="sender">The object that raised the event.</param>
        /// <param name="message">The message instance.</param>
        public static void PublishToItem(object target, object sender, object message)
        {
            var wrapper = new WeakHandlerWrapper(target, null, NodoRemoveCallback);
            if (wrapper.HasHandlers)
                wrapper.Handle(sender, message);
        }

        /// <summary>
        ///     Adds the specified handler to the container.
        /// </summary>
        /// <param name="item">The specified handler.</param>
        protected virtual bool AddInternal(object item)
        {
            if (item == this)
                return false;
            if (Listeners.Contains(item))
                return true;
            
            var handlers = new List<object>();
            var handlerWrapper = new WeakHandlerWrapper(item, handlers, RemoveHandler);
            if (handlerWrapper.HasHandlers)
                handlers.Add(handlerWrapper);
            if (handlers.Count == 0)
                return false;
            WeakReference weakReference = Listeners.Add(item);
            ItemsToHandlers[weakReference] = handlers;
            return true;
        }

        /// <summary>
        ///     Removes the specified handler.
        /// </summary>
        /// <param name="item">The specified handler.</param>
        protected virtual bool RemoveInternal(object item)
        {
            WeakReference weakReference = Listeners.FindWeakReference(item);
            if (weakReference == null)
                return false;
            ItemsToHandlers.Remove(weakReference);
            return Listeners.Remove(item);
        }

        /// <summary>
        ///     Determines whether the <see cref="WeakListenersCollection" /> contains a specific value.
        /// </summary>
        /// <returns>
        ///     true if <paramref name="item" /> is found in the <see cref="WeakListenersCollection" />; otherwise, false.
        /// </returns>
        /// <param name="item">
        ///     The object to locate in the <see cref="WeakListenersCollection" />.
        /// </param>
        protected virtual bool ContainsInternal(object item)
        {
            return Listeners.Contains(item);
        }

        /// <summary>
        ///     Removes all listeners.
        /// </summary>
        protected virtual void ClearInternal()
        {
            foreach (object key in Listeners.ToArray())
                RemoveInternal(key);
        }

        /// <summary>
        ///     Notifies listeners about an event.
        /// </summary>
        /// <param name="handlers">The specified listeners to notify.</param>
        /// <param name="sender">The object that raised the event.</param>
        /// <param name="parameter">Information about the event.</param>
        protected virtual void PublishInternal(IList<object> handlers, object sender, object parameter)
        {
            if (handlers.Count == 0) return;
            foreach (var handler in handlers.OfType<IHandler>())
            {
                if (IsNeedHandle(sender, parameter, handler))
                    handler.Handle(sender, parameter);
            }
        }

        /// <summary>
        /// Determines whether the handler is suitable for the notification.
        /// </summary>
        /// <param name="sender">The object that raised the event.</param>
        /// <param name="parameter">Information about the event.</param>
        /// <param name="handler">The specified handler.</param>
        /// <returns>If <c>true</c> is suitable; otherwise <c>false</c>.</returns>
        protected virtual bool IsNeedHandle(object sender, object parameter, object handler)
        {
            return true;
        }

        private void RemoveHandler(List<object> handlers, IHandler handler)
        {
            using (DeadLockDetector.Lock(_locker))
                handlers.Remove(handler);
        }

        #endregion

        #region Implementation of IEnumerable

        /// <summary>
        ///     Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        ///     A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<object> GetEnumerator()
        {
            using (DeadLockDetector.Lock(_locker))
                return Listeners.GetEnumerator();
        }

        /// <summary>
        ///     Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        ///     An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region Implementation of IListenersContainer

        /// <summary>
        ///     Gets the number of elements contained in the <see cref="WeakListenersCollection" />.
        /// </summary>
        /// <returns>
        ///     The number of elements contained in the <see cref="WeakListenersCollection" />.
        /// </returns>
        public int Count
        {
            get
            {
                return Listeners.Count;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
        /// </summary>
        /// <returns>
        /// true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
        /// </returns>
        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        ///     Adds the specified listener to the container.
        /// </summary>
        /// <param name="item">The specified listener.</param>
        public bool Add(object item)
        {
            Should.NotBeNull(item, "item");
            using (DeadLockDetector.Lock(_locker))
                return AddInternal(item);
        }

        /// <summary>
        ///     Adds the specified listener to the container.
        /// </summary>
        /// <param name="item">The specified listener.</param>
        void ICollection<object>.Add(object item)
        {
            Add(item);
        }

        /// <summary>
        /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
        /// </summary>
        /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.
        ///                 </param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.
        ///                 </param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.
        ///                 </exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.
        ///                 </exception><exception cref="T:System.ArgumentException"><paramref name="array"/> is multidimensional.
        ///                     -or-
        ///                 <paramref name="arrayIndex"/> is equal to or greater than the length of <paramref name="array"/>.
        ///                     -or-
        ///                     The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.
        ///                     -or-        
        ///                 </exception>
        public void CopyTo(object[] array, int arrayIndex)
        {
            Should.NotBeNull(array, "array");
            Listeners.CopyTo(array, arrayIndex);
        }

        /// <summary>
        ///     Removes the specified listener.
        /// </summary>
        /// <param name="item">The specified listener.</param>
        public bool Remove(object item)
        {
            Should.NotBeNull(item, "item");
            using (DeadLockDetector.Lock(_locker))
                return RemoveInternal(item);
        }

        /// <summary>
        ///     Determines whether the <see cref="WeakListenersCollection" /> contains a specific value.
        /// </summary>
        /// <returns>
        ///     true if <paramref name="item" /> is found in the <see cref="WeakListenersCollection" />; otherwise, false.
        /// </returns>
        /// <param name="item">
        ///     The object to locate in the <see cref="WeakListenersCollection" />.
        /// </param>
        public bool Contains(object item)
        {
            Should.NotBeNull(item, "item");
            using (DeadLockDetector.Lock(_locker))
                return Listeners.Contains(item);
        }

        /// <summary>
        ///     Removes all listeners.
        /// </summary>
        public void Clear()
        {
            object[] objects;
            using (DeadLockDetector.Lock(_locker))
                objects = Listeners.ToArray();

            foreach (object key in objects)
                Remove(key);
        }

        /// <summary>
        ///     Notifies listeners about an event.
        /// </summary>
        /// <param name="sender">The object that raised the event.</param>
        /// <param name="message">The message instance.</param>
        public void Publish(object sender, object message)
        {
            Should.NotBeNull(sender, "sender");
            Should.NotBeNull(message, "message");
            var messageSenderCache = new MessageSenderCache(sender, message);
            using (DeadLockDetector.Lock(_updatedMessages))
            {
                if (_updatedMessages.ContainsKey(messageSenderCache)) return;
                _updatedMessages[messageSenderCache] = true;
            }
            object[] handlers;
            using (DeadLockDetector.Lock(_locker))
            {
                handlers = ItemsToHandlers
                    .Where(pair => pair.Key.Target != null && !pair.Key.Target.Equals(sender))
                    .SelectMany(pair => pair.Value)
                    .ToArray();
            }
            if (handlers.Length != 0)
                PublishInternal(handlers, sender, message);
            using (DeadLockDetector.Lock(_updatedMessages))
            {
                _updatedMessages.Remove(messageSenderCache);
            }
        }

        /// <summary>
        ///     Notifies listener about an event.
        /// </summary>
        /// <param name="sender">The object that raised the event.</param>
        /// <param name="target">The specified listener to notify.</param>
        /// <param name="message">The message instance.</param>
        public void Publish(object sender, object target, object message)
        {
            Should.NotBeNull(sender, "sender");
            Should.NotBeNull(target, "target");
            Should.NotBeNull(message, "message");
            if (!Contains(target)) return;

            var messageSenderCache = new MessageSenderCache(sender, message);
            using (DeadLockDetector.Lock(_updatedMessages))
            {
                if (_updatedMessages.ContainsKey(messageSenderCache)) return;
                _updatedMessages[messageSenderCache] = true;
            }
            object[] handlers;
            using (DeadLockDetector.Lock(_locker))
            {
                handlers = ItemsToHandlers
                    .Where(pair => pair.Key.Target == target && !pair.Key.Target.Equals(sender))
                    .SelectMany(pair => pair.Value)
                    .ToArray();
            }
            if (handlers.Length != 0)
                PublishInternal(handlers, sender, message);
            using (DeadLockDetector.Lock(_updatedMessages))
            {
                _updatedMessages.Remove(messageSenderCache);
            }
        }

        #endregion
    }
}